package org.hs.phd.odi.transformation;

import java.awt.geom.Point2D;


public class ParaCatadioptricTransformation implements ODITransformation{

	private final double principalPointX;
	private final double principalPointY;
	private final double focalLength;
	private final boolean isMirrorLookingDown;

	public ParaCatadioptricTransformation(double principalPointX, 
			double principalPointY, double focalLength, 
			boolean isMirrorLookingDown)
	{
		this.principalPointX = principalPointX;
		this.principalPointY = principalPointY;
		this.focalLength = focalLength;
		this.isMirrorLookingDown = isMirrorLookingDown;
		
	}
	
	@Override
	public Point2D.Double imageToSphere(double x, double y) {
		
		double u = x - principalPointX;
		double v = y - principalPointY;
		
		double longitude = Math.atan2( v, u ); 

		//convert from (-pi,pi) interval to (0, 2pi) interval
		double phi = (longitude >= 0 ) ? longitude : 2*Math.PI + longitude;		

		double half90MinusTheta = Math.atan( Math.sqrt(u*u +v*v) / ( 2*focalLength));
		
		assert half90MinusTheta >= 0 && half90MinusTheta <= Math.PI/2;

		double a90MinusTheta = 2*half90MinusTheta;


		double theta = Math.PI - a90MinusTheta;
		
		return new Point2D.Double(theta, phi);
	}

	@Override
	public Point2D.Double sphereToImage(double theta, double phi) {
		
		//This projection will not calculate a result if the point is too near to the north pole, 
		//where the projection goes to the infinity
		if( theta < Math.PI/6 ) throw new IllegalArgumentException("Point too close to the north pole goes to infinity on the image plane!");
		
		
		double cosPhi = Math.cos(phi);
		double sinPhi = Math.sin(phi);
		
		double a90MinusTheta = Math.PI - theta;

		double half90MinusTheta =  a90MinusTheta / 2;
		
		
		
		double xyHypothenus = Math.tan( half90MinusTheta )*(2*focalLength);
		
		double u = cosPhi*xyHypothenus;
		double v = sinPhi*xyHypothenus;
		
		double x = u + principalPointX;
		double y = v + principalPointY;
		
		return new Point2D.Double(x, y);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(focalLength);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + (isMirrorLookingDown ? 1231 : 1237);
		temp = Double.doubleToLongBits(principalPointX);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		temp = Double.doubleToLongBits(principalPointY);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ParaCatadioptricTransformation other = (ParaCatadioptricTransformation) obj;
		if (Double.doubleToLongBits(focalLength) != Double.doubleToLongBits(other.focalLength))
			return false;
		if (isMirrorLookingDown != other.isMirrorLookingDown)
			return false;
		if (Double.doubleToLongBits(principalPointX) != Double.doubleToLongBits(other.principalPointX))
			return false;
		if (Double.doubleToLongBits(principalPointY) != Double.doubleToLongBits(other.principalPointY))
			return false;
		return true;
	}

	
}
